; Brother WP2450DS cpmish BIOS © 2020 David Given
; This file is distributable under the terms of the 2-clause BSD license.
; See COPYING.cpmish in the distribution root directory for more information.

    maclib cpm
    maclib cpmish
    maclib wp2450ds

    extrn SYSIN
    extrn SYSOUT
    extrn ADDAHL

    public TTYINIT
    public TTYPUTC
    public TTYPUT8
    public TTYPUT16
    public TTYPUTSI
    public TTYNL
    public TTYHOME
    public CURON
    public CUROFF

    cseg

CURSOR_UPDATES = 0
CLEAR_SCREEN_ON_INIT = 1
EMULATE_CLEAR_TO_EOL = 1
EMULATE_CLEAR_TO_EOS = 0
    maclib tty
    maclib print

TTYINIT equ tty_init
TTYPUTC equ tty_putc
TTYPUT8 equ tty_puthex8
TTYPUT16 equ tty_puthex16
TTYPUTSI equ tty_putsi
TTYNL equ tty_newline
TTYHOME equ tty_home_cursor

tty_rawwrite:
    and 0x7f
    sub 0x20
    push af
    ld de, (tty_cursorx)
    call calc_address
    ex de, hl                ; destination address in de

    pop af

    ld h, 0
    ld l, a
    ld b, h
    ld c, l

    ; Multiply by 7 (the size of a character in the font table):
                             ; 1
    add hl, hl
    add hl, bc               ; 1
    add hl, hl
    add hl, bc               ; 1

    ld bc, font_table
    add hl, bc               ; hl = font addr

    ex de, hl                ; hl = video addr, de = font addr

    ld a, (tty_attributes)
    ld c, 0                  ; reverse video xor value (off)
    bit 0, a
    jr z, not_reverse_video
    dec c                    ; on
not_reverse_video
    
    ; Write one blank line.

    out0 (PORT_VIDEO_ADDR_LO), l
    out0 (PORT_VIDEO_ADDR_HI), h
    nop
    out0 (PORT_VIDEO_DATA_W), c
    call add_91

    ; Write the character data.

    ld b, 7
.2
    out0 (PORT_VIDEO_ADDR_LO), l
    out0 (PORT_VIDEO_ADDR_HI), h
    nop
    ld a, (de)
    xor c
    inc de
    out0 (PORT_VIDEO_DATA_W), a

    call add_91
    djnz .2

    ; Now write another blank line.

    out0 (PORT_VIDEO_ADDR_LO), l
    out0 (PORT_VIDEO_ADDR_HI), h
    nop
    out0 (PORT_VIDEO_DATA_W), c

    ret

; Adds 91 to HL.
add_91:
    ld a, 91
    add l
    ld l, a
    ret nc
    inc h
    ret

; On entry, D=y, E=x
calc_address:
    ld h, 0
    ld l, d
    ld b, h
    ld c, d

    ; Multiply by 819 (9 x 91):
                            ; 1
    add hl, hl
    add hl, bc              ; 1
    add hl, hl              ; 0
    add hl, hl              ; 0
    add hl, hl
    add hl, bc              ; 1
    add hl, hl
    add hl, bc              ; 1
    add hl, hl              ; 0
    add hl, hl              ; 0
    add hl, hl
    add hl, bc              ; 1
    add hl, hl
    add hl, bc              ; 1

    ld a, l
    add e
    ld l, a
    rnc
    inc h
    ret

tty_clear_to_eos:
    call tty_clear_to_eol
    ld a, (tty_cursory)
    inc a
    cp SCREEN_HEIGHT
    ret z                   ; return if we're on the last line of the screen
    ld d, a
    ld e, 0
    call calc_address       ; hl is the address of the beginning of the next line

    ld de, SCREEN_WIDTH * SCREEN_HEIGHT * CHAR_HEIGHT
    ex de, hl               ; DE = video memory address, HL = size of video memory
    or a                    ; clear C
    sbc hl, de              ; calculate amount of video memory to clear
    ; fall through
; Clear HL bytes from video memory address DE
clear_bytes:
    out0 (PORT_VIDEO_ADDR_LO), e
    out0 (PORT_VIDEO_ADDR_HI), d
    
    ld b, 0
.1
    out0 (PORT_VIDEO_DATA_W), b
    dec hl
    ld a, h
    or l
    jr nz, .1
    ret

    label XXX
tty_delete_line:
    ld a, (tty_cursory)
    cp SCREEN_HEIGHT - 1
    jr z, on_last_line      ; skip scroll if there's nothing to do

    push af
    ld d, a
    ld e, 0
    call calc_address       ; hl = dest address
    pop af

    ld b, a
    ld a, SCREEN_HEIGHT - 1
    sub b                   ; calculate number of text lines to move
    ld b, a
                            ; 1
    add a                   
    add b                   ; 1
    ld d, a                 ; b = number of three-line chunks to move

    ld bc, -(SCREEN_WIDTH*3)
    add hl, bc              ; compensate for preincrement in loop
.1
    ld bc, SCREEN_WIDTH*(CHAR_HEIGHT+3)
    add hl, bc
    call read_chunk

    ld bc, -(SCREEN_WIDTH*CHAR_HEIGHT)
    add hl, bc
    call write_chunk

    dec d
    jr nz, .1

    ; Delete the last line of the screen.

on_last_line:
    ld de, SCREEN_WIDTH*(SCREEN_HEIGHT-1)*CHAR_HEIGHT
    ld hl, SCREEN_WIDTH*CHAR_HEIGHT
    jr clear_bytes

tty_insert_line:
    ld a, (tty_cursory)
    cp SCREEN_HEIGHT - 1
    jr z, on_last_line      ; skip scroll if there's nothing to do

    ; Source vdeo memory address.
    ld hl, SCREEN_WIDTH*SCREEN_HEIGHT*CHAR_HEIGHT - SCREEN_WIDTH*3

    ld b, a
    ld a, SCREEN_HEIGHT - 1
    sub b                   ; calculate number of text lines to move
    ld b, a
                            ; 1
    add a                   
    add b                   ; 1
    ld d, a                 ; b = number of three-line chunks to move

.1
    call read_chunk

    ld bc, -(SCREEN_WIDTH * CHAR_HEIGHT)
    add hl, bc
    call write_chunk

    ld bc, SCREEN_WIDTH * (CHAR_HEIGHT-3)
    add hl, bc

    dec d
    jr nz, .1

    ; Finished; clear the current line.

    ld a, (tty_cursory)
    ld d, a
    ld e, 0
    call calc_address
    ld hl, SCREEN_WIDTH * 1 * CHAR_HEIGHT
    jp clear_bytes

; Loads the chunk at video memory address HL into the buffer.
read_chunk:
    ld a, PORT_VIDEO_DATA_R
    call setup_dma
    ld a, 01000110b         ; One memory wait state, 0 I/O wait state, I/O->memory
    out0 (DCNTL), a
    ld a, 10010000b         ; DE1 enable, DWE0 disable
    out0 (DSTAT), a             ; perform the transfer
wait_for_dma_end:
    in0 a, (DSTAT)
    and 10000000b           ; test for DMA completion
    jr nz, wait_for_dma_end
    ret

; Writes the chunk at video memory address HL from the buffer.
write_chunk:
    ld a, PORT_VIDEO_DATA_W
    call setup_dma
    ld a, 01000100b         ; One memory wait state, 0 I/O wait state, memory->I/O
    out0 (DCNTL), a
    ld a, 10010000b         ; DE1 enable, DWE0 disable
    out0 (DSTAT), a             ; perform the transfer
    jr wait_for_dma_end

; On entry: HL is the video memory address, and A is the I/O port.
setup_dma:
    ld (scanline_dma + IAR1L - MAR1L), a
    out0 (PORT_VIDEO_ADDR_LO), l
    out0 (PORT_VIDEO_ADDR_HI), h
    push hl
    ld b, scanline_dma.end - scanline_dma
    ld c, MAR1L
    ld hl, scanline_dma
    otimr
    pop hl
    ret
scanline_dma:
    dw video_buffer         ; MAR1
    db 0x06                 ; MAR1B
    dw PORT_VIDEO_DATA_W    ; IAR1
    db 0                    ; unused
    dw SCREEN_WIDTH*3       ; BCR1
scanline_dma.end:

toggle_cursor:
    ld de, (tty_cursorx)
    call calc_address

    ld b, 9
.1
    out0 (PORT_VIDEO_ADDR_LO), l
    out0 (PORT_VIDEO_ADDR_HI), h
    nop
    in0 a, (PORT_VIDEO_DATA_R)
    xra 0xff
    out0 (PORT_VIDEO_ADDR_LO), l
    out0 (PORT_VIDEO_ADDR_HI), h
    nop
    out0 (PORT_VIDEO_DATA_W), a

    call add_91
    djnz .1
    ret

CURON:
cursor_state equ $ + 1
    ld a, 0
    or a
    call z, toggle_cursor
    ld a, 1
    ld (cursor_state), a
    ret

CUROFF:
    ld a, (cursor_state)
    or a
    call nz, toggle_cursor
    xor a
    ld (cursor_state), a
    ret

video_buffer:
    ds 91*3

font_table:
    include "font.inc"
font_table_end:

; vim: ts=4 sw=4 et ft=asm

